package cn.wmw.xyy.service.impl;

import cn.wmw.result.JsonResult;
import cn.wmw.utils.CodeGenerateUtils;
import cn.wmw.xyy.bo.KillCoursePreOrderBo;
import cn.wmw.xyy.domain.Course;
import cn.wmw.xyy.domain.CourseMarket;
import cn.wmw.xyy.domain.KillActivity;
import cn.wmw.xyy.domain.KillCourse;
import cn.wmw.xyy.dto.KillCoursePreOrderDto;
import cn.wmw.xyy.exception.GlobalExceptionEnum;
import cn.wmw.xyy.feign.CourseFeignClient;
import cn.wmw.xyy.mapper.KillCourseMapper;
import cn.wmw.xyy.service.IKillActivityService;
import cn.wmw.xyy.service.IKillCourseService;
import cn.wmw.xyy.utils.AssertUtil;
import cn.wmw.xyy.vo.CourseInfoVo;
import cn.wmw.xyy.vo.CourseOrderInfoVo;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.mapper.EntityWrapper;
import com.baomidou.mybatisplus.service.impl.ServiceImpl;
import org.redisson.api.RSemaphore;
import org.redisson.api.RedissonClient;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;

/**
 * <p>
 *  服务实现类
 * </p>
 *
 * @author wmw
 * @since 2023-01-13
 */
@Service
public class KillCourseServiceImpl extends ServiceImpl<KillCourseMapper, KillCourse> implements IKillCourseService {

    @Autowired
    private CourseFeignClient courseFeignClient;

    @Autowired
    private IKillActivityService killActivityService;

    @Autowired
    private RedisTemplate<Object,Object> redisTemplate;


    @Autowired
    private RedissonClient redissonClient;


    /**
     * 保存秒杀课程数据
     * @param killCourse
     */
    @Override
    public void saveKillCourse(KillCourse killCourse) {
        //查询课程
        JsonResult info = courseFeignClient.info(killCourse.getCourseId().toString());
        AssertUtil.isTrue(info.isSuccess(), GlobalExceptionEnum.ERROR);
        Object infoTmp = info.getData();
        AssertUtil.isNotNull(infoTmp, GlobalExceptionEnum.ERROR);
        // 课程相关信息
        CourseOrderInfoVo courseOrderInfoVo = JSON.parseObject(JSON.toJSONString(infoTmp), CourseOrderInfoVo.class);
        List<CourseInfoVo> courseInfos = courseOrderInfoVo.getCourseInfos();
        // 课程是否存在
        AssertUtil.isTrue(courseInfos.size() > 0, GlobalExceptionEnum.ERROR);
        CourseInfoVo courseInfoVo = courseInfos.get(0);
        // 课程的状态是否为上架
        Course course = courseInfoVo.getCourse();
        AssertUtil.isTrue(Course.ONLINE.equals(course.getStatus()), GlobalExceptionEnum.ERROR);
        // 课程是否收费
        CourseMarket courseMarket = courseInfoVo.getCourseMarket();
        AssertUtil.isTrue(CourseMarket.NOTFREE.equals(courseMarket.getCharge()), GlobalExceptionEnum.ERROR);
        // 课程是否已经存在于活动中
        KillCourse killCourseTmp = selectKillCourseByCourseIdAndKillActivityId(killCourse.getActivityId(), killCourse.getCourseId());
        AssertUtil.isNull(killCourseTmp, GlobalExceptionEnum.ERROR);
        // 秒杀活动是否已经创建
        KillActivity killActivity = killActivityService.selectById(killCourse.getActivityId());
        AssertUtil.isNotNull(killActivity, GlobalExceptionEnum.ERROR);
        // 活动是否已经开始
        Date now = new Date();
        Date beginTime = killActivity.getBeginTime();
        boolean isBefore = now.before(beginTime);
        AssertUtil.isTrue(isBefore, GlobalExceptionEnum.ERROR);
        // 判断活动状态是否为未发布
        AssertUtil.isTrue(KillActivity.WAIT_PUBLISH.equals(killActivity.getPublishStatus()), GlobalExceptionEnum.ERROR);
        // 存数据
        initKillCourse(killActivity, killCourse);
        insert(killCourse);
    }

    /**
     * 查询该活动id下的所有课程
     * @param killActivityId
     * @return
     */
    @Override
    public List<KillCourse> selectByKillActivityId(Long killActivityId) {
        return selectList(new EntityWrapper<KillCourse>().eq("activity_id", killActivityId));
    }

    /**
     * 查询所有发布了的课程
     * @return
     */
    @Override
    public List<KillCourse> getOnlineAll() {
        Set<Object> keys = redisTemplate.keys("activity:*");
        List<KillCourse> killCourses = new ArrayList<>();
        // 循环遍历将课程信息取出
        keys.forEach(key->{
            List killCourseListTmp = redisTemplate.opsForHash().values(key);
            killCourses.addAll(killCourseListTmp);
        });
        return killCourses;
    }

    @Override
    public KillCourse getOnlineOne(Long killCourseId, Long killActivityId) {
        Object course= redisTemplate.opsForHash().get("activity:" + killActivityId, killCourseId.toString());
        AssertUtil.isNotNull(course, GlobalExceptionEnum.ERROR);
        return (KillCourse) course;
    }

    @Override
    public String kill(KillCoursePreOrderDto killCoursePreOrderDto) {
        Date date = new Date();
        Long activityId = killCoursePreOrderDto.getActivityId();
        Long killCourseId = killCoursePreOrderDto.getKillCourseId();
        Long loginId = 7L;
        String orderNo = CodeGenerateUtils.generateOrderSn(loginId);
        Object o = redisTemplate.opsForHash().get("activity:" + activityId, killCourseId.toString());
        AssertUtil.isNotNull(o, GlobalExceptionEnum.ERROR);
        KillCourse killCourse = JSON.parseObject(JSON.toJSONString(o), KillCourse.class);
        // 2.2.校验当前用户是否秒杀过此课程  压测时需注释掉
//        Object isBuy = redisTemplate.opsForValue().get(loginId + ":" + activityId + ":" + killCourseId);
//        AssertUtil.isNull(isBuy, GlobalExceptionEnum.ERROR);
        // 3.扣减库存
        RSemaphore semaphore = redissonClient.getSemaphore(activityId + ":" + killCourseId);
        AssertUtil.isNotNull(semaphore, GlobalExceptionEnum.ERROR);
        boolean isTryAcquire = semaphore.tryAcquire(1);
        AssertUtil.isTrue(isTryAcquire, GlobalExceptionEnum.ERROR);
        // 4.生成预创订单
        KillCoursePreOrderBo killCoursePreOrderBo = new KillCoursePreOrderBo();
        BeanUtils.copyProperties(killCourse, killCoursePreOrderBo);
        killCoursePreOrderBo.setKillCourseId(killCourseId);
        killCoursePreOrderBo.setOrderNo(orderNo);
        killCoursePreOrderBo.setUserId(loginId);
        // 4.1.存储预创订单到redis
        Long timeOut = killCourse.getEndTime().getTime() - date.getTime();
        redisTemplate.opsForValue().set("preOrder:" + orderNo, killCoursePreOrderBo, timeOut, TimeUnit.MILLISECONDS);
        // 4.2.存储用户秒杀过课程的信息到Redis
        redisTemplate.opsForValue().set(loginId + ":" + activityId + ":" + killCourseId, orderNo, timeOut, TimeUnit.MILLISECONDS);
//        System.out.println("秒杀耗时：" + (System.currentTimeMillis() - date.getTime()));
        return orderNo;
    }

    @Override
    public CourseOrderInfoVo oneByOrder(String orderNo) {
        Object o = redisTemplate.opsForValue().get("preOrder:" + orderNo);
        AssertUtil.isNotNull(o, GlobalExceptionEnum.ERROR);
        KillCoursePreOrderBo killCoursePreOrderBo = JSON.parseObject(JSON.toJSONString(o), KillCoursePreOrderBo.class);
        // 3.初始化装数据容器
        List<CourseInfoVo> courseInfoVos = new ArrayList<>();
        CourseOrderInfoVo courseOrderInfoVo = new CourseOrderInfoVo();
        // 4.组合数据到每个课程信息对象中
        CourseInfoVo courseInfoVo = initCourseInfoVo(killCoursePreOrderBo);
        courseInfoVos.add(courseInfoVo);
        // 5.放入数据到大vo中
        courseOrderInfoVo.setCourseInfos(courseInfoVos);
        courseOrderInfoVo.setTotalAmount(killCoursePreOrderBo.getKillPrice());
        return courseOrderInfoVo;
    }

    private CourseInfoVo initCourseInfoVo(KillCoursePreOrderBo killCoursePreOrderBo) {
        CourseInfoVo courseInfoVo = new CourseInfoVo();
        Course course = new Course();
        course.setLoginId(killCoursePreOrderBo.getUserId());
        course.setName(killCoursePreOrderBo.getCourseName());
        course.setPic(killCoursePreOrderBo.getCoursePic());
        courseInfoVo.setCourse(course);
        CourseMarket courseMarket = new CourseMarket();
        courseMarket.setPrice(killCoursePreOrderBo.getKillPrice());
        courseInfoVo.setCourseMarket(courseMarket);

        return courseInfoVo;
    }

    private KillCourse selectKillCourseByCourseIdAndKillActivityId(Long activityId, Long courseId) {
        return selectOne(new EntityWrapper<KillCourse>()
                .eq("activity_id", activityId)
                .eq("course_id", courseId));
    }

    private void initKillCourse(KillActivity killActivity, KillCourse killCourse) {
        killCourse.setCreateTime(new Date());
        killCourse.setStartTime(killActivity.getBeginTime());
        killCourse.setEndTime(killActivity.getEndTime());
        killCourse.setPublishStatus(KillActivity.WAIT_PUBLISH);
        killCourse.setTimeStr(killActivity.getTimeStr());
    }
}
